package com.atguigu.gmall.order.service.impl;

import com.atguigu.gmall.common.constant.RedisConst;
import com.atguigu.gmall.feign.cart.CartFeignClient;
import com.atguigu.gmall.feign.product.SkuInfoFeignClient;
import com.atguigu.gmall.feign.ware.WareFeignClient;
import com.atguigu.gmall.model.enums.OrderStatus;
import com.atguigu.gmall.model.enums.ProcessStatus;
import com.atguigu.gmall.model.order.OrderDetail;
import com.atguigu.gmall.model.order.OrderInfo;
import com.atguigu.gmall.mq.RabbitConst;
import com.atguigu.gmall.order.config.OrderProperties;
import com.atguigu.gmall.order.mapper.OrderDetailMapper;
import com.atguigu.gmall.order.mapper.OrderInfoMapper;
import com.atguigu.gmall.order.service.OrderService;
import com.atguigu.gmall.order.to.OrderSpiltDetailTo;
import com.atguigu.gmall.order.to.OrderSplitTo;
import com.atguigu.gmall.order.to.WareSkuTo;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * @author lsj
 * @create 2021-08-27 16:03
 */
@Service
public class OrderServiceImpl extends ServiceImpl<OrderInfoMapper,OrderInfo> implements OrderService {

    @Autowired
    StringRedisTemplate redisTemplate;

    @Autowired
    SkuInfoFeignClient skuInfoFeignClient;

    @Autowired
    WareFeignClient wareFeignClient;

    @Autowired
    CartFeignClient cartFeignClient;

    @Autowired
    ThreadPoolExecutor executor;

    @Autowired
    OrderProperties orderProperties;

    @Autowired
    OrderInfoMapper orderInfoMapper;

    @Autowired
    OrderDetailMapper orderDetailMapper;

    @Autowired
    RabbitTemplate rabbitTemplate;

    @Override
    public boolean checkToken(String userId, String tradeNo) {
        ValueOperations<String, String> operations = redisTemplate.opsForValue();
        //原子验令牌
        //前端带请求进来。拿到令牌就删除
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] " +
                "then return redis.call('del', KEYS[1]) else return 0 end";
//        String token = operations.get(RedisConst.USER_UNREPEAT_TOKEN + userId);
//        //令牌用完就删,防止前端恶意重试 30min内试uuid。
//        redisTemplate.delete(RedisConst.USER_UNREPEAT_TOKEN + userId);
        Long execute = redisTemplate.execute(
                new DefaultRedisScript<Long>(script,Long.class),
                Arrays.asList(RedisConst.USER_UNREPEAT_TOKEN + userId),
                tradeNo);


        if(!StringUtils.isEmpty(tradeNo) && execute.intValue() != 0){
            //验令牌成功
            return true;
        }
        return false;
    }

    /**
     * 验价、验库存
     * @param orderDetailList
     * @return
     */
    @Override
    public List<String> checkSkuPriceAndStock(List<OrderDetail> orderDetailList) {

        //这个集合保存接下来我们给线程池提交的所有异步任务
        List<CompletableFuture<Void>> futures = new ArrayList<>();


        // add不用担心导致线程安全问题
        List<String> errMsg = new ArrayList<>(); //HashMap


        if(!CollectionUtils.isEmpty(orderDetailList)){
            //遍历每一个需要购买的商品  10  20
            for (OrderDetail detailVo : orderDetailList) {
                Long skuId = detailVo.getSkuId();
                String skuName = detailVo.getSkuName();
                BigDecimal orderPrice = detailVo.getOrderPrice();
                Integer skuNum = detailVo.getSkuNum();

                //1远程返回的最新价格。有一个验证失败就不往下执行
                //【用户体验】2验证所有，即使失败，继续往下校验
                CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
                    //不用老请求就可以
                    BigDecimal skuPrice = skuInfoFeignClient.getSkuPrice(skuId);
                    if (!orderPrice.equals(skuPrice)) {
                        errMsg.add(skuName + "：价格发生变化，请刷新确认");
                    }
                }, executor);

                CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> {
                    // 该项目的验库存、锁库存没有做成原子性，即表示可超卖系统，若没有了叫补货就行，用户等不及取消订单就行
                    // 若是要做成原子性，两个办法：
                    //      ① 远程系统支持连验证+锁库存一起做。
                    //      ② 分布式锁+数据库事务。除了减库存，还要生成各种其他信息。
                    // 如下仅仅是验库存
                    String stock = wareFeignClient.hasStock(skuId, skuNum);
                    if (stock.equals("0")) {
                        errMsg.add(skuName + "：库存不足");
                    }
                }, executor);

                futures.add(future);
                futures.add(future1);
            }
        }


        //以前收集到的所有异步任务全部结束，才能算结束
        CompletableFuture<?>[] futureArray = new CompletableFuture[futures.size()];
        for (int i =0;i<futures.size();i++){
            futureArray[i] = futures.get(i);
        }

        CompletableFuture.allOf(futureArray).join();



        return errMsg;
    }

    /**
     * 创建订单  order_info   order_detail
     * @param orderInfo
     * @return
     */
    @Transactional
    @Override
    public OrderInfo createOrder(OrderInfo orderInfo) {
        //1、保存订单信息 order_info
        orderInfo.sumTotalAmount();  //总价格计算
        //订单状态 ， 默认未支付
        orderInfo.setOrderStatus(OrderStatus.UNPAID.name());
        //用户id，controller透传过来的已经设置好了

        //out_trade_no； 和支付宝对接的唯一流水号
        //给支付宝给一个你订单的唯一标识（对外流水号），问支付宝某个单是否支付成功。
        //订单id（对内流水号）
        //生成的对外流水号
        orderInfo.setOutTradeNo("ATGUIGU"+System.currentTimeMillis()+new Random().nextInt(10000));
        //订单创建时间
        Date date = new Date();
        orderInfo.setCreateTime(date);

        //expire_time 过期时间。模拟 properties.getTtl()
        //下一次要支付这个订单之前，判断一下，这个订单如果是未支付而且过期了，就不给支付
        Calendar instance = Calendar.getInstance();
        //当前时间+30min
        instance.add(Calendar.MINUTE,orderProperties.getTtl()); //订单的过期时间是可配置的
        Date time = instance.getTime();
        orderInfo.setExpireTime(time);

        //process_status 订单进度状态，默认未支付
        orderInfo.setProcessStatus(ProcessStatus.UNPAID.name());
//        img_url ，任意给一个图片，如下就是设为集合中第一个物品的图片
        List<OrderDetail> detailList = orderInfo.getOrderDetailList();
        orderInfo.setImgUrl(detailList.get(0).getImgUrl());

        // 订单数据插入数据库
        int insert = orderInfoMapper.insert(orderInfo);

        //2、保存订单详情：保存订单中所有的商品信息，假设有十个商品
        for (OrderDetail detail : detailList) {
            //填充数据库需要的字段
            Long id = orderInfo.getId();
            detail.setOrderId(id);
            detail.setCreateTime(new Date());
        }
        //批量保存订单详情的方法，只和数据库交互一次
        orderDetailMapper.saveBatch(detailList);

        //订单创建完后的收尾工作
        afterOrderCreated(orderInfo);
        return orderInfo;
    }

    //是超时的订单，我们就关单，
    //秒 分 时  日 月 周
//    @Scheduled(cron = "0 0/30 * * * *")  会有时效性问题
    @Override
    public void closeOrder(Long id) {
        //去数据库修改订单状态
//        if ()  //判断如果当前订单是UPAID
        //扫描所有未关闭的订单
        OrderInfo orderInfo = orderInfoMapper.selectById(id);
        if(orderInfo.getOrderStatus().equals(OrderStatus.UNPAID.name())){
            String name = OrderStatus.CLOSED.name();
            orderInfoMapper.updateOrderStatus(id,name);
        }
    }

    @Override
    public void changeOrderStatusPayed(String out_trade_no, String name) {
        orderInfoMapper.updateOrderStatusPayed(out_trade_no,name);
    }

    private void afterOrderCreated(OrderInfo orderInfo) {
        //1、把购物车中相关的商品删除即可
        List<Long> skuIds = new ArrayList<>();
        List<OrderDetail> detailList = orderInfo.getOrderDetailList();
        for (OrderDetail detail : detailList) {
            skuIds.add(detail.getSkuId());
        }
        //远程调用之前，因为不是异步线程，所以feign的拦截器自己会把老请求放过去
        cartFeignClient.deleteSkuFromCart(skuIds);


        //2、发送一个消息（定时关单——采用RabbitMQ:
        //                  参数1：交换机的名称，
        //                  参数2：延时队列的路由键-消息活着时的那个队列的路由键
        //                  参数3：要放入的消息）
        rabbitTemplate.convertAndSend(RabbitConst.ORDER_EXCHANGE,RabbitConst.CREATE_ORDER_RK,orderInfo.getId());
//        rabbitTemplate.convertAndSend();
//        rabbitTemplate
//        scheduledExecutorService.schedule(() -> {
//            //关单
//            closeOrder(orderInfo.getId());
//        }, 30, TimeUnit.MINUTES);

    }

    @Override
    public OrderInfo getOrderInfoWithDetailsByOutTradeNo(String outTradeNo) {
        QueryWrapper<OrderInfo> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("out_trade_no", outTradeNo);
        OrderInfo orderInfo = orderInfoMapper.selectOne(queryWrapper);


        QueryWrapper<OrderDetail> wrapper = new QueryWrapper<>();
        wrapper.eq("order_id",orderInfo.getId());
        List<OrderDetail> details = orderDetailMapper.selectList(wrapper);
        orderInfo.setOrderDetailList(details);

        return orderInfo;
    }

    @Override
    public List<OrderSplitTo> splitOrder(Long orderId, String wareSkuMap) throws JsonProcessingException {
        //1、原始订单信息
        OrderInfo orderInfo = getOrderInfoWithDetailsById(orderId);

        //2、订单中商品的库存分布  [{"wareId":"1","skuIds":["2","10"]},{"wareId":"2","skuIds":["3"]}]
        List<WareSkuTo> wareSkuTos = new ObjectMapper().readValue(wareSkuMap, new TypeReference<List<WareSkuTo>>() {
        });

        //3、拆单，
        List<OrderSplitTo> orderSplitTos = new ArrayList<>();
        for (WareSkuTo skuTo : wareSkuTos) {
            OrderSplitTo splitTo = new OrderSplitTo();
            //仓库id
            String wareId = skuTo.getWareId();
            //上面仓库所有的商品id
            List<String> skuIds = skuTo.getSkuIds();
            //TODO 把原始订单拆分多个继续保存数据库
            OrderInfo info = new OrderInfo();
            BeanUtils.copyProperties(orderInfo,info);
            info.setId(null);
            info.setParentOrderId(orderInfo.getId());
            //1、订单的商品详情拿来
            QueryWrapper<OrderDetail> queryWrapper = new QueryWrapper<>();
            queryWrapper.in("sku_id",skuIds);
            queryWrapper.eq("order_id",orderInfo.getId());
            List<OrderDetail> details = orderDetailMapper.selectList(queryWrapper);
            info.setOrderDetailList(details);
            //2、重新计算拆分的订单的价格，必须保证setOrderDetailList有东西
            info.sumTotalAmount();
            //记录拆分了的这个单是为哪个仓库拆分的
            //3、保存拆分后的订单
            orderInfoMapper.insert(info);

            //4、封装个库存服务的信息
            BeanUtils.copyProperties(info,splitTo);
            splitTo.setPaymentWay("2");
            splitTo.setWareId(wareId);
            //把刚才插入的子订单id获取到
            splitTo.setOrderId(info.getId().toString());
            //拆完订单以后，每个订单中包含哪些商品
            List<OrderSpiltDetailTo> detail = new ArrayList<>();
            for (OrderDetail orderDetail : details) {
                detail.add(new OrderSpiltDetailTo(orderDetail.getSkuId().toString(),orderDetail.getSkuNum(),orderDetail.getSkuName()));
            }

            splitTo.setDetails(detail);

            //收集所有拆了单
            orderSplitTos.add(splitTo);

        }

        //修改原始单状态变为已拆分
        orderInfoMapper.updateOrderStatus(orderInfo.getId(),OrderStatus.SPLIT.name());

        return orderSplitTos;
    }

    @Override
    public void changeOrderStatus(long parseLong, OrderStatus waitingDelever) {
        orderInfoMapper.updateOrderStatus(parseLong,waitingDelever.name());
    }

    @Override
    public OrderInfo getOrderInfoWithDetailsById(Long orderId) {
        OrderInfo orderInfo = orderInfoMapper.selectById(orderId);

        QueryWrapper<OrderDetail> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("order_id",orderId);
        List<OrderDetail> details = orderDetailMapper.selectList(queryWrapper);

        orderInfo.setOrderDetailList(details);

        return orderInfo;

    }


}

